// addrspace.cc 
//	Routines to manage address spaces (executing user programs).
//
//	In order to run a user program, you must:
//
//	1. link with the -N -T 0 option 
//	2. run coff2noff to convert the object file to Nachos format
//		(Nachos object code format is essentially just a simpler
//		version of the UNIX executable object code format)
//	3. load the NOFF file into the Nachos file system
//		(if you haven't implemented the file system yet, you
//		don't need to do this last step)
//
// Copyright (c) 1992-1993 The Regents of the University of California.
// All rights reserved.  See copyright.h for copyright notice and limitation 
// of liability and disclaimer of warranty provisions.

#include "copyright.h"
#include "system.h"
#include "addrspace.h"
#include "noff.h"
#include "table.h"
#include "synch.h"

extern "C" { int bzero(char *, int); };

//Lock for threads to access the numpages to read/modify
Lock numPagesLock("NUMPAGESLOCK");

Table::Table(int s) : map(s), table(0), lock(0), size(s) {
    table = new void *[size];
    lock = new Lock("TableLock");
}

Table::~Table() {
    if (table) {
	delete table;
	table = 0;
    }
    if (lock) {
	delete lock;
	lock = 0;
    }
}

void *Table::Get(int i) {
    // Return the element associated with the given if, or 0 if
    // there is none.

    return (i >=0 && i < size && map.Test(i)) ? table[i] : 0;
}

int Table::Put(void *f) {
    // Put the element in the table and return the slot it used.  Use a
    // lock so 2 files don't get the same space.
    int i;	// to find the next slot

    lock->Acquire();
    i = map.Find();
    lock->Release();
    if ( i != -1)
	table[i] = f;
    return i;
}

void Table::Update(int i, void *f) {
	// Replaces the element at index i with the element.
	
	lock->Acquire();
	if ( map.Test(i)) {
		table[i] = f;
	}
	lock->Release();
}

void *Table::Remove(int i) {
    // Remove the element associated with identifier i from the table,
    // and return it.

    void *f =0;

    if ( i >= 0 && i < size ) {
	lock->Acquire();
	if ( map.Test(i) ) {
	    map.Clear(i);
	    f = table[i];
	    table[i] = 0;
	}
	lock->Release();
    }
    return f;
}

int Table::NumFilled() {
	return size - map.NumClear();
}

//----------------------------------------------------------------------
// SwapHeader
// 	Do little endian to big endian conversion on the bytes in the 
//	object file header, in case the file was generated on a little
//	endian machine, and we're now running on a big endian machine.
//----------------------------------------------------------------------

static void 
SwapHeader (NoffHeader *noffH)
{
	noffH->noffMagic = WordToHost(noffH->noffMagic);
	noffH->code.size = WordToHost(noffH->code.size);
	noffH->code.virtualAddr = WordToHost(noffH->code.virtualAddr);
	noffH->code.inFileAddr = WordToHost(noffH->code.inFileAddr);
	noffH->initData.size = WordToHost(noffH->initData.size);
	noffH->initData.virtualAddr = WordToHost(noffH->initData.virtualAddr);
	noffH->initData.inFileAddr = WordToHost(noffH->initData.inFileAddr);
	noffH->uninitData.size = WordToHost(noffH->uninitData.size);
	noffH->uninitData.virtualAddr = WordToHost(noffH->uninitData.virtualAddr);
	noffH->uninitData.inFileAddr = WordToHost(noffH->uninitData.inFileAddr);
}

//----------------------------------------------------------------------
// AddrSpace::AddrSpace
// 	Create an address space to run a user program.
//	Load the program from a file "executable", and set everything
//	up so that we can start executing user instructions.
//
//	Assumes that the object code file is in NOFF format.
//
//	"executable" is the file containing the object code to load into memory
//
//      It's possible to fail to fully construct the address space for
//      several reasons, including being unable to allocate memory,
//      and being unable to read key parts of the executable.
//      Incompletely consretucted address spaces have the member
//      constructed set to false.
//----------------------------------------------------------------------

AddrSpace::AddrSpace(OpenFile *executable) : fileTable(MaxOpenFiles) { // magic number should remove
    NoffHeader noffH;
    unsigned int i, size;
	unsigned int numExecPages; // The number of pages that must be read in from the executable
	unsigned int physicalAddress;
	spaceId = -1;
	
    // Don't allocate the input or output to disk files
    fileTable.Put(0);
    fileTable.Put(0);

    executable->ReadAt((char *)&noffH, sizeof(noffH), 0);
    if ((noffH.noffMagic != NOFFMAGIC) && 
		(WordToHost(noffH.noffMagic) == NOFFMAGIC))
    	SwapHeader(&noffH);
    ASSERT(noffH.noffMagic == NOFFMAGIC);

    size = noffH.code.size + noffH.initData.size + noffH.uninitData.size ;
	numPagesLock.Acquire();
    numPages = divRoundUp(size, PageSize) + divRoundUp(UserStackSize,PageSize);// magic number should remove
                                                // we need to increase the size
						// to leave room for the stack
	numPagesLock.Release();
	numExecPages = divRoundUp(size, PageSize); 
    size = numPages * PageSize;

    //ASSERT(numPages <= NumPhysPages);		// check we're not trying
						// to run anything too big --
						// at least until we have
						// virtual memory

	execFile = executable;
	
	printf("AddressSpace.cc Const: inFileAddr = %d.\n", noffH.code.inFileAddr);
	codeInFileAddr = noffH.code.inFileAddr;
						
    DEBUG('a', "Initializing address space, num pages %d, size %d\n", 
					numPages, size);
// first, set up the translation 
    pageTable = new PageTableEntry[numPages];
    
	// Initialize each page in the page table by asking the system which free physcial page to bind to.
	int selectedPage; // ID of the next open physical page
	for (i = 0; i < numPages; i++) {
		//selectedPage = physicalPageTable->Put(this);
		//if(selectedPage >= 0){
			pageTable[i].virtualPage = i;	// for now, virtual page # = phys page #
			pageTable[i].physicalPage = -1; // page fault handler will find the physPage for us
			pageTable[i].valid = TRUE;
			pageTable[i].use = FALSE;
			pageTable[i].dirty = FALSE;
			pageTable[i].readOnly = FALSE;  // if the code segment was entirely on 
							// a separate page, we could set its 
							// pages to be read-only
			pageTable[i].processId = spaceId;
			//pageTable[i].pageType = PAGE_STACK;
			pageTable[i].pageLocation = PAGE_LOC_UNKNOWN;
			pageTable[i].timestamp = 0;
			
			// If this page is still part of the executable file, we should pre-load this page into memory now.
			//physicalAddress = selectedPage*PageSize;
			if(i < numExecPages){
				/*executable->ReadAt(&(machine->mainMemory[physicalAddress]),
						PageSize, (noffH.code.inFileAddr+(i*PageSize)));
				DEBUG('a', "Loaded page into physical memory.\n");*/
				// No longer preloading the executable
				pageTable[i].pageLocation = PAGE_EXECUTABLE;
			}
		//}
		//else {
		//	DEBUG('a', "No valid physical pages available.\n");
		//	interrupt->Halt();
		//}
    }
    
// zero out the entire address space, to zero the unitialized data segment 
// and the stack segment
   // bzero(machine->mainMemory, size);

// then, copy in the code and data segments into memory
	//RestorePageTable();
   /* if (noffH.code.size > 0) {
        DEBUG('a', "Initializing code segment, at 0x%x, size %d\n", 
			noffH.code.virtualAddr, noffH.code.size);
		int* physicalAddr;
		machine->Translate(noffH.code.virtualAddr, physicalAddr, noffH.code.size, FALSE);
        executable->ReadAt(&(machine->mainMemory[*physicalAddr]),
			noffH.code.size, noffH.code.inFileAddr);
    }
    if (noffH.initData.size > 0) {
        DEBUG('a', "Initializing data segment, at 0x%x, size %d\n", 
			noffH.initData.virtualAddr, noffH.initData.size);
		int physicalAddr;
		machine->Translate(noffH.initData.virtualAddr, &physicalAddr, noffH.initData.size, FALSE);
        executable->ReadAt(&(machine->mainMemory[physicalAddr]),
			noffH.initData.size, noffH.initData.inFileAddr);
    }*/

}

//----------------------------------------------------------------------
// AddrSpace::~AddrSpace
//
// 	Dealloate an address space.  release pages, page tables, files
// 	and file tables
//----------------------------------------------------------------------

AddrSpace::~AddrSpace()
{
	// Deallocate all of our pages in physical memory.
	numPagesLock.Acquire();
	for(unsigned int i = 0; i < numPages;++i){
		physicalPageTable->Remove(pageTable[i].physicalPage);
	}
	numPagesLock.Release();
    delete pageTable;
}

//----------------------------------------------------------------------
// AddrSpace::InitRegisters
// 	Set the initial values for the user-level register set.
//
// 	We write these directly into the "machine" registers, so
//	that we can immediately jump to user code.  Note that these
//	will be saved/restored into the currentThread->userRegisters
//	when this thread is context switched out.
//----------------------------------------------------------------------

void
AddrSpace::InitRegisters()
{
	 
	numPagesLock.Acquire();
    int i;

    for (i = 0; i < NumTotalRegs; i++)
	machine->WriteRegister(i, 0);

    // Initial program counter -- must be location of "Start"
    machine->WriteRegister(PCReg, 0);	

    // Need to also tell MIPS where next instruction is, because
    // of branch delay possibility
    machine->WriteRegister(NextPCReg, 4);

   // Set the stack register to the end of the address space, where we
   // allocated the stack; but subtract off a bit, to make sure we don't
   // accidentally reference off the end!
	//Never causes race condition because this function is only used when an address space is initialized.
	
    machine->WriteRegister(StackReg, numPages * PageSize - 16);
    DEBUG('a', "Initializing stack register to %x\n", numPages * PageSize - 16);
	numPagesLock.Release();
	 
}

//----------------------------------------------------------------------
// AddrSpace::SaveState
// 	On a context switch, save any machine state, specific
//	to this address space, that needs saving.
//----------------------------------------------------------------------

void AddrSpace::SaveState() 
{
	
	
	for (int i = 0; i < TLBSize; i++) {
		if (machine->tlb[i].valid == TRUE && machine->tlb[i].dirty == TRUE){
			// TODO: Tell Brandon bit is dirty
			printf("TLB entry %d is dirty. It was invalidated on a context switch and should be written back.\n", i);
			((PageTableEntry*) invertedPageTable->Get(machine->tlb[i].physicalPage))->dirty = 
				machine->tlb[i].dirty;
		}
		machine->tlb[i].valid = FALSE;
	}
}

//----------------------------------------------------------------------
// AddrSpace::RestoreState
// 	On a context switch, restore the machine state so that
//	this address space can run.
//
//      tell the machine where to find the page table.
//----------------------------------------------------------------------

void AddrSpace::RestoreState() 
{
	//machine->pageTable = pageTable;
    //machine->pageTableSize = numPages;
}

//----------------------------------------------------------------------
// AddrSpace::addStack()
// 	Allocates physical memory for a new stack for a new thread.
//
// 	Uses a lock to modify numpages and the bitmap.
//  Uses the bitmap to allocate physical pages for the stack.
//	Adds the stack at the end of the address space in virtual memory.
//	Then updates the actual number of pages.
//	The lock is only to prevent new threads being created at the same time
//	From changing it at the same time since new address spaces have their own numPages.
//	
//----------------------------------------------------------------------

void AddrSpace::addStack(){
	//Lock to protect numPages from other threads being created.
	numPagesLock.Acquire();
	int selectedPage;
	for(int i = 0; i < (divRoundUp(UserStackSize,PageSize));i++){
		//selectedPage = physicalPageTable->Put(this);
		//if(selectedPage >= 0){
			//Add in entries for the stack at the end of the virtual address space
			pageTable[numPages+i].virtualPage = numPages+i;
			pageTable[numPages+i].physicalPage = selectedPage;
			pageTable[numPages+i].valid = TRUE;
			pageTable[numPages+i].use = FALSE;
			pageTable[numPages+i].dirty = FALSE;
			pageTable[numPages+i].readOnly = FALSE;  // if the code segment was entirely on 
					// a separate page, we could set its 
					// pages to be read-only
			pageTable[numPages+i].processId = spaceId;
			pageTable[numPages+i].pageType = PAGE_STACK;
			pageTable[numPages+i].pageLocation = PAGE_LOC_UNKNOWN;
			pageTable[numPages+i].timestamp = 0;
		//}
		//else{
		//	DEBUG('a', "No valid physical pages available.\n");
		//	interrupt->Halt();
		//}
	}
	

	numPages = numPages + divRoundUp(UserStackSize,PageSize);
	// Set the stack register to the end of the address space, where we
   // allocated the stack; but subtract off a bit, to make sure we don't
   // accidentally reference off the end!
	
    machine->WriteRegister(StackReg, numPages * PageSize - 16);
    DEBUG('b', "Initializing stack register to %x\n", numPages * PageSize - 16);
	numPagesLock.Release();
	
}

unsigned int AddrSpace::getNumPages(){
	 
	numPagesLock.Acquire();
	int temp = numPages;
	numPagesLock.Release();
	 
	return temp;
}

PageTableEntry* AddrSpace::getPageTableEntry(unsigned int vpn){
	//check that vpn is within acceptable range (>0 and <numPages)
	if (vpn < 0 || vpn > numPages){
		DEBUG('a',"ERROR: vpn value %d is outside of acceptable range.\n",vpn);
		//if not, call interrupt->halt()
		interrupt->Halt();
	}
	else if (pageTable[vpn].valid == FALSE){
		//check pageTable[vpn].valid
		DEBUG('a',"ERROR: vpn value %d is not valid.\n",vpn);
		interrupt->Halt();
	}
	return &pageTable[vpn];
}

void AddrSpace::readFromExecutable(unsigned int physicalPage, unsigned int vpn) {
	// Assuming at this point that the given vpn is in the executable.
	ASSERT(pageTable[vpn].pageLocation == PAGE_EXECUTABLE);
	//printf("readFromExecutable: physPage = %d, vpn = %d.\n", physicalPage, vpn);
	execFile->ReadAt(&(machine->mainMemory[physicalPage*PageSize]),
				PageSize, (vpn*PageSize) + codeInFileAddr);
}
